package net.oxygen.upload.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import net.oxygen.upload.dto.FileChunkDTO;
import net.oxygen.upload.dto.FileInfoDTO;
import net.oxygen.upload.request.FileChunkInitTaskReq;
import net.oxygen.upload.request.FileChunkMergeReq;
import net.oxygen.upload.request.FileSecondUploadReq;
import net.oxygen.upload.request.FileUploadReq;
import net.oxygen.upload.service.IFileChunkService;
import net.oxygen.upload.service.IFileV2Service;
import net.oxygen.upload.util.JsonData;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

/**
 * 文件上传控制器V2
 * <p>
 * 提供文件上传、查询、删除以及分片上传相关接口
 * 本控制器是优化后的版本，提供了更完善的功能：
 * - 文件列表查询和删除
 * - 小文件直接上传
 * - 文件秒传功能
 * - 大文件分片上传
 * - 上传进度查询
 * - 分片合并等功能
 * </p>
 *
 * @author chenweiwei
 * @version 2.0
 * @since 2023-03-24
 */
@CrossOrigin
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/upload/v2")
@Tag(name = "文件上传管理", description = "文件上传和管理接口，包含上传、秒传、分片上传和文件管理")
public class UploadV2Controller {

    /**
     * 文件服务接口
     */
    private final IFileV2Service fileV2Service;

    /**
     * 文件分片服务接口
     */
    private final IFileChunkService fileChunkService;

    /**
     * 获取文件列表
     * <p>
     * 根据文件名模糊查询已上传的文件列表，支持分页查询
     * </p>
     *
     * @param fileName 文件名关键字，用于模糊查询，可为空
     * @param pageNum  页码，默认为1
     * @param pageSize 每页记录数，默认为10
     * @return 文件信息列表，包含文件基本信息和预签名URL
     */
    @GetMapping("list")
    @Operation(summary = "获取文件列表", description = "根据文件名关键字查询已上传的文件列表，支持分页")
    @Parameters({
            @Parameter(name = "fileName", description = "文件名关键字，用于模糊查询", in = ParameterIn.QUERY, required = false, schema = @Schema(type = "string")),
            @Parameter(name = "pageNum", description = "页码，默认为1", in = ParameterIn.QUERY, required = false, schema = @Schema(type = "integer", defaultValue = "1")),
            @Parameter(name = "pageSize", description = "每页记录数，默认为10", in = ParameterIn.QUERY, required = false, schema = @Schema(type = "integer", defaultValue = "10"))
    })
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = FileInfoDTO.class))),
            @ApiResponse(responseCode = "500", description = "服务器错误")
    })
    public JsonData list(@RequestParam(required = false) String fileName,
            @RequestParam(required = false, defaultValue = "1") Integer pageNum,
            @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
        List<FileInfoDTO> fileList = fileV2Service.list(fileName, pageNum, pageSize);
        return JsonData.success(fileList);
    }

    /**
     * 删除文件
     * <p>
     * 根据文件唯一标识（MD5值）删除文件，包括从存储中删除文件和数据库记录
     * </p>
     *
     * @param identifier 文件唯一标识（MD5值）
     * @return 操作结果
     */
    @DeleteMapping("remove/{identifier}")
    @Operation(summary = "删除文件", description = "根据文件唯一标识删除文件，包括存储和数据库记录")
    @Parameter(name = "identifier", description = "文件唯一标识（MD5值）", in = ParameterIn.PATH, required = true, schema = @Schema(type = "string"))
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "删除成功"),
            @ApiResponse(responseCode = "404", description = "文件不存在"),
            @ApiResponse(responseCode = "500", description = "服务器错误")
    })
    public JsonData delete(@PathVariable("identifier") String identifier) {
        fileV2Service.delete(identifier);
        return JsonData.success();
    }

    /**
     * 小文件上传
     * <p>
     * 用于上传较小的文件，直接将文件上传到存储服务
     * </p>
     *
     * @param req 文件上传请求，包含文件对象和相关信息
     * @return 上传结果
     */
    @PostMapping("upload")
    @Operation(summary = "小文件上传", description = "上传小体积文件，适用于小于5MB的文件")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "上传成功"),
            @ApiResponse(responseCode = "400", description = "请求参数错误"),
            @ApiResponse(responseCode = "500", description = "服务器错误")
    })
    public JsonData upload(FileUploadReq req) {
        fileV2Service.upload(req);
        return JsonData.success();
    }

    /**
     * 秒传文件
     * <p>
     * 基于文件MD5特征值判断服务器是否已存在相同文件，如存在则直接返回成功，无需实际上传
     * </p>
     *
     * @param req 秒传请求对象，包含文件唯一标识、文件名、大小等信息
     * @return 是否秒传成功
     */
    @PostMapping("second_upload")
    @Operation(summary = "秒传文件", description = "基于MD5判断文件是否已存在，无需实际上传文件内容")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "请求成功处理，返回是否秒传成功的结果"),
            @ApiResponse(responseCode = "400", description = "请求参数错误"),
            @ApiResponse(responseCode = "500", description = "服务器错误")
    })
    public JsonData secondUpload(@RequestBody FileSecondUploadReq req) {
        boolean uploadSuccess = fileV2Service.secondUpload(req);
        return JsonData.success(uploadSuccess);
    }

    /**
     * 初始化文件分片上传任务
     * <p>
     * 创建大文件分片上传任务，生成唯一的uploadId
     * </p>
     *
     * @param req 初始化分片任务请求，包含文件信息、分片大小等
     * @return 分片任务信息，包含uploadId和分片数量
     */
    @PostMapping("init_file_chunk_task")
    @Operation(summary = "初始化文件分片上传任务", description = "创建大文件分片上传任务，生成uploadId")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "初始化成功"),
            @ApiResponse(responseCode = "400", description = "请求参数错误"),
            @ApiResponse(responseCode = "500", description = "服务器错误")
    })
    public JsonData initFileChunkTask(@RequestBody FileChunkInitTaskReq req) {
        FileChunkDTO fileChunkDTO = fileChunkService.initFileChunkTask(req);
        return JsonData.success(fileChunkDTO);
    }

    /**
     * 获取文件分片上传URL
     * <p>
     * 生成预签名URL用于直接上传分片到存储服务（已废弃，推荐使用upload_part接口）
     * </p>
     *
     * @param identifier 文件唯一标识
     * @param partNumber 分片序号，从1开始
     * @return 预签名的上传URL
     */
    @GetMapping("get_file_chunk_upload_url/{identifier}/{partNumber}")
    @Operation(summary = "获取文件分片上传URL（已废弃）", description = "获取文件分片上传URL（已废弃，请使用 upload_part 接口）")
    @Parameters({
            @Parameter(name = "identifier", description = "文件唯一标识（MD5值）", in = ParameterIn.PATH, required = true, schema = @Schema(type = "string")),
            @Parameter(name = "partNumber", description = "分片序号，从1开始", in = ParameterIn.PATH, required = true, schema = @Schema(type = "integer"))
    })
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "获取URL成功"),
            @ApiResponse(responseCode = "404", description = "上传任务不存在"),
            @ApiResponse(responseCode = "500", description = "服务器错误")
    })
    @Deprecated
    public JsonData getFileChunkUploadUrl(@PathVariable("identifier") String identifier,
            @PathVariable("partNumber") int partNumber) {
        String url = fileChunkService.genPreSignUploadUrl(identifier, partNumber);
        return JsonData.success(url);
    }

    /**
     * 合并文件分片
     * <p>
     * 将上传完成的所有分片合并成完整文件
     * </p>
     *
     * @param req 合并分片请求，包含文件唯一标识等信息
     * @return 合并结果
     */
    @PostMapping("merge_file_chunk")
    @Operation(summary = "合并文件分片", description = "合并已上传的所有分片为完整文件")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "合并成功"),
            @ApiResponse(responseCode = "400", description = "合并失败，可能是部分分片未上传"),
            @ApiResponse(responseCode = "404", description = "上传任务不存在"),
            @ApiResponse(responseCode = "500", description = "服务器错误")
    })
    public JsonData mergeFileChunk(@RequestBody FileChunkMergeReq req) {
        fileChunkService.mergeFileChunk(req);
        return JsonData.success();
    }

    /**
     * 获取文件分片上传进度
     * <p>
     * 查询已上传的分片信息，用于断点续传
     * </p>
     *
     * @param identifier 文件唯一标识
     * @return 文件分片信息，包含已上传分片列表
     */
    @GetMapping("file_chunk_upload_progress/{identifier}")
    @Operation(summary = "获取文件分片上传进度", description = "查询已上传的分片信息，用于断点续传")
    @Parameter(name = "identifier", description = "文件唯一标识（MD5值）", in = ParameterIn.PATH, required = true, schema = @Schema(type = "string"))
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "查询成功"),
            @ApiResponse(responseCode = "404", description = "上传任务不存在"),
            @ApiResponse(responseCode = "500", description = "服务器错误")
    })
    public JsonData getFileChunkUploadProgress(@PathVariable("identifier") String identifier) {
        FileChunkDTO fileChunkUploadProgressDTO = fileChunkService.listFileChunk(identifier);
        return JsonData.success(fileChunkUploadProgressDTO);
    }

    /**
     * 直接上传分片
     * <p>
     * 通过服务端代理将分片上传到存储服务，避免客户端直接与存储服务通信
     * </p>
     *
     * @param identifier 文件唯一标识
     * @param partNumber 分片序号，从1开始
     * @param file       分片文件
     * @return 上传结果
     */
    @PostMapping("upload_part/{identifier}/{partNumber}")
    @Operation(summary = "直接上传分片", description = "通过服务器代理上传分片到存储服务")
    @Parameters({
            @Parameter(name = "identifier", description = "文件唯一标识（MD5值）", in = ParameterIn.PATH, required = true, schema = @Schema(type = "string")),
            @Parameter(name = "partNumber", description = "分片序号，从1开始", in = ParameterIn.PATH, required = true, schema = @Schema(type = "integer")),
            @Parameter(name = "file", description = "分片文件内容", required = true, schema = @Schema(type = "file"))
    })
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "上传成功"),
            @ApiResponse(responseCode = "400", description = "请求参数错误"),
            @ApiResponse(responseCode = "404", description = "上传任务不存在"),
            @ApiResponse(responseCode = "500", description = "服务器错误")
    })
    public JsonData uploadPart(@PathVariable("identifier") String identifier,
            @PathVariable("partNumber") int partNumber,
            @RequestParam("file") MultipartFile file) {
        try {
            boolean success = fileChunkService.uploadPart(identifier, partNumber, file.getBytes());
            return JsonData.success(success);
        } catch (Exception e) {
            return JsonData.error("上传分片失败：" + e.getMessage());
        }
    }
}
